using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using SolarWinds.Helpers;
using SolarWinds.Input;

namespace SolarWinds.Widgets
{
    public class WidgetManager : DrawableComponentManager, IWidgetContainer
    {
        public event EventHandler<FocusEvent> MouseOverWidgetChanged;
        public event EventHandler<FocusEvent> KeyFocusChanged;

        private readonly List<Widget> mChildren = new List<Widget>();
        private Widget mMouseOverWidget;
        private Widget mKeyFocusWidget;
        
        private IInputManagerService mInputService;

        #region Properties

        public Widget MouseOverWidget
        {
            get { return mMouseOverWidget; }
        }

        public Widget KeyFocusWidget
        {
            get { return mKeyFocusWidget; }
        }

        #endregion

        #region IWidgetManagerService Members

        public List<Widget> Widgets
        {
            get { return mChildren; }
        }

        #endregion

        #region Constructor

        public WidgetManager(Microsoft.Xna.Framework.Game game)
            : base(game)
        {
        }

        #endregion

        #region Initialize / Dispose

        public override void Initialize()
        {
            mInputService = (IInputManagerService)
                            Game.Services.GetService(typeof(IInputManagerService));

            InstallHandlers();            

            base.Initialize();
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                RemoveHandlers();
            }

            base.Dispose(disposing);
        }

        #endregion

        #region Install / Remove Keyboard and Mouse EventHandlers

        private void InstallHandlers()
        {
            mInputService.Keyboard.KeyClicked += OnKeyClicked;
            mInputService.Keyboard.KeyPressed += OnKeyPressed;
            mInputService.Keyboard.KeyReleased += OnKeyReleased;

            mInputService.Mouse.ButtonPressed += OnMousePressed;
            mInputService.Mouse.ButtonReleased += OnMouseReleased;
            mInputService.Mouse.ButtonClicked += OnMouseClicked;
            mInputService.Mouse.ButtonDoubleClicked += OnMouseDoubleClicked;
            mInputService.Mouse.Moved += OnMouseMoved;
        }

        private void RemoveHandlers()
        {
            mInputService.Keyboard.KeyClicked -= OnKeyClicked;
            mInputService.Keyboard.KeyPressed -= OnKeyPressed;
            mInputService.Keyboard.KeyReleased -= OnKeyReleased;

            mInputService.Mouse.ButtonPressed -= OnMousePressed;
            mInputService.Mouse.ButtonReleased -= OnMouseReleased;
            mInputService.Mouse.ButtonClicked -= OnMouseClicked;
            mInputService.Mouse.ButtonDoubleClicked -= OnMouseDoubleClicked;
            mInputService.Mouse.Moved -= OnMouseMoved;
        }

        #endregion

        #region Key EventHandlers

        private void OnKeyClicked(object sender, KeyEvent e)
        {
            if (!Enabled)
            {
                return;
            }

            if (mKeyFocusWidget != null &&
                mKeyFocusWidget.Enabled)
            {
                mKeyFocusWidget.KeyClickedEvent(e);
            }
        }

        private void OnKeyPressed(object sender, KeyEvent e)
        {
            if (!Enabled)
            {
                return;
            }

            if (mKeyFocusWidget != null &&
                mKeyFocusWidget.Enabled)
            {
                mKeyFocusWidget.KeyPressedEvent(e);
            }
        }

        private void OnKeyReleased(object sender, KeyEvent e)
        {
            if (!Enabled)
            {
                return;
            }

            if (e.Key == Keys.Tab)
            {
                if (InputModifiers.Shift == (InputModifiers.Shift & e.Modifiers))
                {
                    FocusNextWidget(e, true, -1);
                }
                else
                {
                    FocusNextWidget(e, true, 1);
                }
            }

            if (e.Key == Keys.Up)
            {
                FocusNextWidget(e, true, -1);
            }

            if (e.Key == Keys.Down)
            {
                FocusNextWidget(e, true, 1);
            }

            if (mKeyFocusWidget != null &&
                mKeyFocusWidget.Enabled)
            {
                mKeyFocusWidget.KeyReleasedEvent(e);
            }
        }

        #endregion

        #region Mouse EventHandlers

        private void OnMousePressed(object sender, MouseEvent e)
        {
            if (!Enabled)
            {
                return;
            }

            if (mMouseOverWidget != null &&
                mMouseOverWidget.Enabled)
            {
                Widget root = FindRoot(mMouseOverWidget);

                // move window of current widget to front
                if (mChildren.Remove(root))
                {
                    mChildren.Add(root);                    
                }

                mMouseOverWidget.MousePressEvent(e);
            }

            if (mMouseOverWidget != null &&
                mMouseOverWidget.Tabable &&
                mKeyFocusWidget != mMouseOverWidget)
            {
                var ke = new KeyEvent(e.Time, InputModifiers.None, Keys.None);

                if (mKeyFocusWidget != null)
                {
                    mKeyFocusWidget.HasKeyFocus = false;
                    mKeyFocusWidget.KeyFocusOutEvent(ke);    
                }
                
                if (mMouseOverWidget != null)
                {
                    mMouseOverWidget.HasKeyFocus = true;
                    mMouseOverWidget.KeyFocusInEvent(ke);
                }

                mKeyFocusWidget = mMouseOverWidget;
            }
        }

        private static Widget FindRoot(Widget widget)
        {
            Widget w = widget;

            while (w.Parent != null)
            {
                w = w.Parent;          
            }

            return w;
        }

        private void OnMouseReleased(object sender, MouseEvent e)
        {
            if (!Enabled)
            {
                return;
            }

            if (mMouseOverWidget != null &&
                mMouseOverWidget.Enabled)
            {
                mMouseOverWidget.MouseReleaseEvent(e);
            }
        }

        private void OnMouseClicked(object sender, MouseEvent e)
        {
            if (!Enabled)
            {
                return;
            }

            if (mMouseOverWidget != null &&
                mMouseOverWidget.Enabled)
            {
                mMouseOverWidget.MouseClickEvent(e);
            }
        }

        private void OnMouseDoubleClicked(object sender, MouseEvent e)
        {
            if (!Enabled)
            {
                return;
            }

            if (mMouseOverWidget != null &&
                mMouseOverWidget.Enabled)
            {
                mMouseOverWidget.MouseDoubleClickEvent(e);
            }
        }

        private void OnMouseMoved(object sender, MouseEvent e)
        {
            if (!Enabled)
            {
                return;
            }

            UpdateMouseOverWidget(e);

            /* This is faster but due to sync problem of mouse and widget
             * it produces some errors
             * if (mCurrentMouseWidget != null)
            {
                Widget root = FindRoot(mCurrentMouseWidget);

                CallChildMoveEvents(root, e);
            }*/

            foreach (Widget w in mChildren)
            {
                CallChildMoveEvents(w, e);
            }
        }

        private static void CallChildMoveEvents(Widget w, MouseEvent e)
        {
            if (w == null) return;

            // TODO introduce a CapturesMouse flag for each widget
            // widget will only be informed about the mouse move,
            // if this flag is set true or the mouse is over 
            // the widget (see Qt behaviour)
            if (w.Enabled)
            {
                w.MouseMoveEvent(e);                
            }

            foreach (Widget child in w.mChildren)
            {
                CallChildMoveEvents(child, e);
            }
        }

        #endregion

        #region Add / Remove Children

        public bool AddChild(Widget widget)
        {
            bool added = false;

            if (widget != null &&
                widget.mParent == null &&
                !mChildren.Contains(widget))
            {
                Components.Add(widget);

                int index = mChildren.BinarySearch(widget, TabOrderComparer.Default);
                if (index < 0)
                {
                    index = ~index;
                    while ((index < mChildren.Count) &&
                           (mChildren[index].TabOrder == widget.TabOrder))
                    {
                        index++;
                    }

                    mChildren.Insert(index, widget);
                    widget.TabOrderChanged += OnWidgetTabOrderChanged;
                }

                added = true;
            }

            return added;
        }

        void OnWidgetTabOrderChanged(object sender, EventArgs e)
        {
            var widget = sender as Widget;
            mChildren.Remove(widget);
            int index = mChildren.BinarySearch(widget, TabOrderComparer.Default);
            if (index < 0)
            {
                index = ~index;
                while ((index < mChildren.Count) &&
                       (mChildren[index].UpdateOrder == widget.TabOrder))
                {
                    index++;
                }
                mChildren.Insert(index, widget);
            }
        }

        public bool RemoveChild(Widget widget)
        {
            bool removed = true;

            if (widget != null &&
                mChildren.Contains(widget))
            {
                removed &= Components.Remove(widget);
                removed &= mChildren.Remove(widget);
                widget.mParent = null;

                widget.TabOrderChanged -= OnWidgetTabOrderChanged;
            }

            return removed;
        }

        #endregion

        #region Update Mouse Over Widget

        private void UpdateMouseOverWidget(MouseEvent e)
        {
            Point absPos = e.AbsolutePosition;

            // store the old
            Widget oldMouseOverWidget = mMouseOverWidget;

            // find the new 
            Widget newMouseOverWidget = FindCurrentMouseWidget(absPos);

            if (oldMouseOverWidget != newMouseOverWidget)
            {
                // focus has changed
                if (oldMouseOverWidget != null)
                {
                    oldMouseOverWidget.IsMouseOver = false;
                    oldMouseOverWidget.MouseOutEvent(e);
                }

                if (newMouseOverWidget != null)
                {
                    newMouseOverWidget.IsMouseOver = true;
                    newMouseOverWidget.MouseOverEvent(e);
                }

                mMouseOverWidget = newMouseOverWidget;

                OnMouseWidgetChanged(new FocusEvent(oldMouseOverWidget, newMouseOverWidget));
            }
        }

        private void OnMouseWidgetChanged(FocusEvent e)
        {
            if (MouseOverWidgetChanged != null)
            {
                MouseOverWidgetChanged(this, e);
            }
        }

        public Widget FindCurrentMouseWidget(Point point)
        {
            Widget w = null;
            FindCurrentMouseWidget(point, this, ref w);

            return w;
        }

        private static void FindCurrentMouseWidget(Point point, 
                                                   IWidgetContainer container, ref Widget newFocus)
        {
            Widget[] widgets = container.Children;
            for (int i = container.Count - 1; i >= 0; --i)
            {
                // Check if this widget gets the focus ...
                if (widgets[i].Visible && 
                    widgets[i].Contains(point))
                {
                    newFocus = widgets[i];

                    FindCurrentMouseWidget(point, widgets[i], ref newFocus);

                    break;
                }
            }
        }

        #endregion
        
        #region Update Key Focus

        private void FocusNextWidget(KeyEvent e, bool movedown, int inc)
        {
            if (mKeyFocusWidget != null)
            {
                mKeyFocusWidget.HasKeyFocus = false;
                mKeyFocusWidget.KeyFocusOutEvent(e);
            }

            if (mKeyFocusWidget == null)
            {
                if (mChildren.Count > 0)
                {
                    if (inc > 0) mKeyFocusWidget = mChildren[0];
                    else if (inc < 0) mKeyFocusWidget = mChildren[mChildren.Count - 1];
                }
            }
            else
            {
                if (movedown && mKeyFocusWidget.mChildren.Count > 0)
                {
                    if (inc > 0) mKeyFocusWidget = mKeyFocusWidget.mChildren[0];
                    else if (inc < 0) mKeyFocusWidget = mKeyFocusWidget.mChildren[mKeyFocusWidget.mChildren.Count - 1];
                }
                else
                {
                    // find the parent of the focus widget
                    if (mKeyFocusWidget.mParent == null) // parent is the widget manager
                    {
                        if (mChildren.Contains(mKeyFocusWidget))
                        {
                            int index = mChildren.IndexOf(mKeyFocusWidget);
                            index += inc;

                            if (index >= mChildren.Count) index = 0;
                            else if (index < 0) index = mChildren.Count - 1;

                            mKeyFocusWidget = mChildren[index];
                        }
                        else // widget does not belong to anything
                        {
                            mKeyFocusWidget = null;
                        }
                    }
                    else // parent is a widget
                    {
                        int index = mKeyFocusWidget.mParent.mChildren.IndexOf(mKeyFocusWidget);
                        index += inc;

                        if (index < mKeyFocusWidget.mParent.mChildren.Count &&
                            index >= 0)
                        {
                            mKeyFocusWidget = mKeyFocusWidget.mParent.mChildren[index];
                        }
                        else
                        {
                            mKeyFocusWidget = mKeyFocusWidget.mParent;
                            FocusNextWidget(e, false, inc);
                        }
                    }
                }
            }

            if (mKeyFocusWidget != null)
            {
                if (mKeyFocusWidget.Tabable)
                {
                    mKeyFocusWidget.HasKeyFocus = true;
                    mKeyFocusWidget.KeyFocusInEvent(e);
                }
                else
                {
                    FocusNextWidget(e, true, inc);
                }
            }
        }

        #endregion

        #region IWidgetContainer Members

        public int Count
        {
            get { return mChildren.Count; }
        }

        public Widget[] Children
        {
            get { return mChildren.ToArray(); }
        }

        #endregion
    }
}